package com.azt.front.interceptor;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.TimestampValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.defaults.DefaultSqlSession;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * mybatis时间拦截
 * 提前确保每张表都要有createtime 与  updatetime
 * Created by 张栋 on 2016/10/20 9:47m
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisDataInterceptor implements Interceptor {
    private static final Log logger = LogFactory.getLog(MybatisDataInterceptor.class);
    private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("-yyyy-MM-dd HH:mm:ss.SSS-");
    private Properties props = null;
    //获取配置参数
    String createDateColumn, updateDateColumn;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        String interceptMethod = invocation.getMethod().getName();
        if (!"prepare".equals(interceptMethod)) {
            return invocation.proceed();
        }

        StatementHandler handler = (StatementHandler) PluginUtil.processTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(handler);
        MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        SqlCommandType sqlCmdType = ms.getSqlCommandType();
        if (sqlCmdType != SqlCommandType.UPDATE && sqlCmdType != SqlCommandType.INSERT) {
            return invocation.proceed();
        }

        if (null == props || props.isEmpty()) {
            return invocation.proceed();
        }
        createDateColumn = props.getProperty("createDateColumn", "createtime");
        updateDateColumn = props.getProperty("updateDateColumn", "updatetime");
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Iterator<ParameterMapping> iterator = parameterMappings.iterator();
        while (iterator.hasNext()) {
            ParameterMapping next = iterator.next();
            String property = next.getProperty();
            if (property.equals(createDateColumn) || property.equals(updateDateColumn)) {
                iterator.remove();
            }
        }
        //获取原始sql
        String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
        //追加参数
        String newSql = "";
        if (sqlCmdType == SqlCommandType.UPDATE && updateDateColumn.length() > 0) {
            newSql = addUpdateData(parameterObject, originalSql, updateDateColumn);
        } else if (sqlCmdType == SqlCommandType.INSERT && createDateColumn.length() > 0) {
            newSql = addInsertData(parameterObject, originalSql, createDateColumn);
        }
        //修改原始sql
        if (newSql.length() > 0) {
            logger.debug("==> newSql: " + newSql);
            metaObject.setValue("delegate.boundSql.sql", newSql);
        }




        return invocation.proceed();

    }

    private String addInsertData(Object parameterObject, String originalSql, String createDateColumn) {
        String sql = originalSql;
        sql = addInsertData(parameterObject, sql, createDateColumn, null, new TimestampValue(TIMESTAMP_FORMAT.format(new Date())));
        return sql;
    }

    private String addInsertData(Object parameterObject, String originalSql, String createDateColumn, String fieldName, Expression expression) {
        try {
            Statement stmt = CCJSqlParserUtil.parse(originalSql);
            if (!(stmt instanceof Insert)) {
                return originalSql;
            }
            Insert update = (Insert) stmt;
            List<Column> columns = update.getColumns();

            Integer createresult = contains(columns, createDateColumn);
            //是否没有createtime
            boolean nocreate = createresult == null;

            if (nocreate) {
                Column createColumn = new Column();
                createColumn.setColumnName(createDateColumn);
                columns.add(createColumn);
            }

            Integer updateresult = contains(columns, updateDateColumn);
            //是否没有updatetime
            boolean noupdate = updateresult == null;

            //创建时,没有updatetime 则添加updatetime
            if (noupdate) {
                Column updateColumn = new Column();
                updateColumn.setColumnName(updateDateColumn);
                columns.add(updateColumn);
            }


            ItemsList itemList = update.getItemsList();
            if (itemList instanceof ExpressionList) {//单个,也有可能是批量接口实际只传递了1个
                ExpressionList expressionList = (ExpressionList) itemList;
                List<Expression> expressions = expressionList.getExpressions();
                if (expression != null) {
                    //没有就添加,否则就替换

                    if (nocreate) {
                        expressions.add(expression);
                    } else {
                        expressions.set(createresult, expression);
                    }

                    if (noupdate) {
                        expressions.add(expression);
                    } else {
                        expressions.set(updateresult, expression);
                    }

                } else {
                    Object value = getFieldValue(parameterObject, 0, fieldName);
                    expressions.add(new StringValue("-" + value.toString() + "-"));
                }
            } else if (itemList instanceof MultiExpressionList) {//批量
                MultiExpressionList multiExpressionList = (MultiExpressionList) itemList;
                List<ExpressionList> expressionLists = multiExpressionList.getExprList();
                for (int i = 0; i < expressionLists.size(); i++) {
                    ExpressionList expressionList = expressionLists.get(i);
                    List<Expression> expressions = expressionList.getExpressions();
                    if (expression != null) {
                        if (nocreate) {
                            expressions.add(expression);
                        } else {
                            expressions.set(createresult, expression);
                        }

                        if (noupdate) {
                            expressions.add(expression);
                        } else {
                            expressions.set(updateresult, expression);
                        }

                    } else {
                        Object value = getFieldValue(parameterObject, i, fieldName);
                        expressions.add(new StringValue("-" + value.toString() + "-"));
                    }
                }
            } else {//insert select
                columns.remove(columns.size() - 1);
            }


            return stmt.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return originalSql;
        }
    }

    private String addUpdateData(Object parameterObject, String originalSql, String updateDateColumn) {
        String sql = originalSql;
        sql = addUpdateData(parameterObject, sql, updateDateColumn, null, new TimestampValue(TIMESTAMP_FORMAT.format(new Date())));
        return sql;
    }

    private String addUpdateData(Object parameterObject, String originalSql, String updateDateColumn, String fieldName, Expression expression) {
        StringBuilder sb = new StringBuilder();
        String sqlList[] = originalSql.split(";");
        for (int i = 0; i < sqlList.length; i++) {
            if (i > 0) {
                sb.append(";");
            }
            try {
                Statement stmt = CCJSqlParserUtil.parse(sqlList[i]);
                if (!(stmt instanceof Update)) {
                    return originalSql;
                }
     /*           Update update = (Update) stmt;
                List<Column> columns = update.getColumns();
                if (contains(columns, updateDateColumn) != null) {
                    sb.append(sqlList[i]);

                    continue;
                }*/

            } catch (Exception e) {
                e.printStackTrace();
                sb.append(sqlList[i]);
                continue;
            }
            if (expression == null) {
                Object value = getFieldValue(parameterObject, i, fieldName);
                String sql = addUpdateDateToSql(sqlList[i], updateDateColumn, new StringValue("-" + value.toString() + "-"));
                sb.append(sql);
            } else {
                String sql = addUpdateDateToSql(sqlList[i], updateDateColumn, expression);
                sb.append(sql);
            }
        }
        return sb.toString();
    }

    private String addUpdateDateToSql(String originalSql, String columnName, Expression expression) {
        try {
            Statement stmt = CCJSqlParserUtil.parse(originalSql);
            if (!(stmt instanceof Update)) {
                return originalSql;
            }
            Update update = (Update) stmt;
            List<Column> columns = update.getColumns();
            if (contains(columns, columnName) == null) {
                Column versionColumn = new Column();
                versionColumn.setColumnName(columnName);
                columns.add(versionColumn);
                List<Expression> expressions = update.getExpressions();
                expressions.add(expression);
            }else{
            	Integer contains = contains(columns, columnName);
            	 List<Expression> expressions = update.getExpressions();
                 expressions.set(contains, expression);
//                columns.remove(contains.intValue());
            }
            
            if (contains(columns, createDateColumn) != null) {
            	 List<Expression> expressions = update.getExpressions();
            	Integer contains = contains(columns, createDateColumn);
            	expressions.remove(contains.intValue());
            	columns.remove(contains.intValue());
            }
            
            
            
            return stmt.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return originalSql;
        }
    }

    @SuppressWarnings("rawtypes")
    private Object getFieldValue(Object parameterObject, int index, String fieldName) {
        Object value = null;
        if (parameterObject instanceof DefaultSqlSession.StrictMap) {
            DefaultSqlSession.StrictMap map = (DefaultSqlSession.StrictMap) parameterObject;
            Object object = map.get("list");
            if (object == null) {
                object = map.get("array");
            }
            if (object != null) {
                value = getValueFromListOrArray(object, index, fieldName);
            }
        } else if (parameterObject instanceof MapperMethod.ParamMap<?>) {
            MapperMethod.ParamMap map = (MapperMethod.ParamMap) parameterObject;
            Object param1 = map.get("param1");
            if (param1.getClass().isArray() || List.class.isAssignableFrom(param1.getClass())) {
                value = getValueFromListOrArray(param1, index, fieldName);
            }
            if (value == null) {
                if (TypeUtil.isSimpleType(param1 == null ? null : param1.getClass())) {
                    Set keys = map.keySet();
                    for (Object key : keys) {
                        if (fieldName.equals(key)) {
                            value = map.get(key);
                            break;
                        }
                    }
                } else {
                    MetaObject metaObject = SystemMetaObject.forObject(param1);
                    value = metaObject.getValue(fieldName);
                }
            }
        } else if (parameterObject instanceof Map) {
            Map map = (Map) parameterObject;
            value = map.get(fieldName);
        } else {
            MetaObject metaObject = SystemMetaObject.forObject(parameterObject);
            value = metaObject.getValue(fieldName);
        }
        if (value == null) {
            throw new IllegalArgumentException("value of[" + fieldName + "]can not be empty");
        }
        return value;
    }

    @SuppressWarnings("rawtypes")
    private Object getValueFromListOrArray(Object parameterObject, int index, String fieldName) {
        Object entity = null;
        if (parameterObject instanceof List) {
            entity = ((List) parameterObject).get(index);
        } else if (parameterObject != null && parameterObject.getClass().isArray()) {
            entity = ((Object[]) parameterObject)[index];
        }
        if (entity != null) {
            MetaObject metaObject = SystemMetaObject.forObject(entity);
            return metaObject.getValue(fieldName);
        }
        return null;
    }

    private Integer contains(List<Column> columns, String columnName) {
        if (columns == null || columns.size() <= 0) {
            return null;
        }
        if (columnName == null || columnName.length() <= 0) {
            return null;
        }
        int i = 0;
        for (Column column : columns) {
            if (column.getColumnName().equalsIgnoreCase(columnName)) {
                return i;
            }
            i++;
        }
        return null;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        if (null != properties && !properties.isEmpty()) props = properties;
    }


    public static final class PluginUtil {

        private PluginUtil() {
        } // private constructor

        /**
         * <p>Recursive get the original target object.
         * <p>If integrate more than a plugin, maybe there are conflict in these plugins, because plugin will proxy the object.<br>
         * So, here get the orignal target object
         *
         * @param target proxy-object
         * @return original target object
         */
        public static Object processTarget(Object target) {
            if (Proxy.isProxyClass(target.getClass())) {
                MetaObject mo = SystemMetaObject.forObject(target);
                return processTarget(mo.getValue("h.target"));
            }
            return target;
        }

    }


    public static class StringUtil {

        public static String underlineToCamel(String param) {
            if (param == null || "".equals(param.trim())) {
                return "";
            }
            int len = param.length();
            StringBuilder sb = new StringBuilder(len);
            for (int i = 0; i < len; i++) {
                char c = param.charAt(i);
                if (c == '_') {
                    if (++i < len) {
                        sb.append(Character.toUpperCase(param.charAt(i)));
                    }
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }

        public static String camelToUnderline(String param) {
            if (param == null || "".equals(param.trim())) {
                return "";
            }
            int len = param.length();
            StringBuilder sb = new StringBuilder(len);
            for (int i = 0; i < len; i++) {
                char c = param.charAt(i);
                if (Character.isUpperCase(c)) {
                    sb.append("_");
                    sb.append(Character.toLowerCase(c));
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }
    }


    public static class TypeUtil {
        public static boolean isSimpleType(Class<?> clazz) {
            if (clazz == String.class) {
                return true;
            } else if (clazz == int.class || clazz == Integer.class) {
                return true;
            } else if (clazz == long.class || clazz == Long.class) {
                return true;
            } else if (clazz == boolean.class || clazz == Boolean.class) {
                return true;
            } else if (clazz == byte.class || clazz == Byte.class) {
                return true;
            } else if (clazz == char.class || clazz == Character.class) {
                return true;
            } else if (clazz == short.class || clazz == Short.class) {
                return true;
            } else if (clazz == float.class || clazz == Float.class) {
                return true;
            } else if (clazz == double.class || clazz == Double.class) {
                return true;
            }
            return false;
        }
    }
    

}
